//-----------------------------------------------------------------------
// <copyright file="SandcastleBuilder.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Jason du Plessis</author>
// <email>jasondp</email>
// <date>2007-01-02</date>
// <summary>Calls Sandcastle Builder in order to generate compiled help documentation for the specified assemblies.</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Tools
{
    using System;
    using System.Xml;
    using System.Globalization;
    using System.IO;
    using System.Text.RegularExpressions;
    using Microsoft.Win32;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Tasks;
    using Microsoft.Build.Utilities;
    using System.Text;    

    /// <summary>
    /// Calls SandcastleBuilder in order to generate compiled HTML documentation for the specified assemblies.
    /// SandcastleBuilder is an open-source tool that packages up  Sandcastle toolset to operate in a similar manner to NDoc
    /// </summary>
    /// <remarks>
    /// <code><![CDATA[
    /// <Tools.SandcastleBuilder Assemblies="assemblies"
    ///              ProjectFilePath="projectFilePath"
    ///              OutputPath="outputFilePath"
    ///              SandcastlePath="sandcastlePath"    
    ///              PresentationStyle="presentationStyle"
    ///              OutputPath="outputPath"
    ///              HelpFileFormat ="helpFileFormat ">
    /// </Tools.SandcastleBuilder>
    /// ]]></code>
    /// <para>where:</para>
    /// <para><i>assemblies (Required)</i></para>
    /// <para>The fullpaths to the assemblies to create docs for.</para>
    /// <para><i>projectFilePath (Required)</i></para>
    /// <para>This will be the Sandcastle Help File Build Project file that will be used as a template.</para>
    /// <para><i>outputFilePath (Optional)</i></para>
    /// <para>The name of the folder to create all the compiled help docs in. The value in the project file will be used if not specified</para>
    /// <para><i>presentationStyle (Optional)</i></para>
    /// <para>Defines the presentation style for the help topic pages. Allowed values are Prototype/VS2005</para>
    /// <para><i>sandcastlePath (Optional)</i></para>
    /// <para>The name of the folder to where the Sandcastle Toolset is installed. The value defined in the project file will be used if not specified</para>
    /// <para><i>helpFileFormat (Optional)</i></para>
    /// <para>Available format are
    ///         HtmlHelp1x HTML Help 1.x format built with HHC.EXE 
    ///         HtmlHelp2x HTML Help 2.x format built with HXCOMP.EXE 
    ///         Website A web site with basic frame set viewer page 
    ///         Help1xAndWebsite An HTML Help 1.x file and web site 
    ///         Help2xAndWebsite An HTML Help 2.x file and web site</para>    
    /// </remarks>
    /// <example>
    /// <code><![CDATA[
    /// <Project>
    ///     <ItemGroup>
    ///         <Assemblies Include="Microsoft.Sdc.Tasks.Configuration.Tasks.dll" />
    ///         <Assemblies Include="Microsoft.Sdc.Tasks.Configuration.Tasks.Test.dll" />
    ///     </ItemGroup>
    ///     <Target Name="Test" >
    ///         <Tools.SandcastleBuilder
    ///             ProjectFilePath="$(MSBuildProjectDirectory)\SandcastleHelpFileBuilder\Main\Main.shfb" 
    ///             OutputPath="$(TEMP)\Main\DocOutput" 
    ///             Assemblies="@(Assemblies->'%(FullPath)')"
    ///             SandcastlePath="$(MSBuildProjectDirectory)\Tools\Sandcastle\Dec06CTP"
    ///             HelpFileFormat="Help1xAndWebsite" >    
    ///         </Tools.SandcastleBuilder>
    ///     </Target>
    /// </Project>
    /// ]]></code>    
    /// </example>
    public class SandcastleBuilder : ToolTaskBase
    {
        /// <summary>
        /// Default CommentsOnly value if not specified in the metadata
        /// </summary>
        private const string CommentsOnlyDefault = "False";

        /// <summary>
        /// Assemblies Task Item List
        /// </summary>
        private ITaskItem[] assemblies;

        /// <summary>
        /// Path to output working files and generated help 
        /// </summary>
        private string outputPath;

        /// <summary>
        /// Defines the presentation style for the help topic pages. Allowed values are Prototype/VS2005
        /// </summary>
        private string presentationStyle;

        /// <summary>
        /// Sandcastle Help File Builder Project Path
        /// </summary>
        private string projectFilePath;

        /// <summary>
        /// Help Format to generated
        /// </summary>
        /// <remarks>
        /// Available formats are
        ///         HtmlHelp1x HTML Help 1.x format built with HHC.EXE 
        ///         HtmlHelp2x HTML Help 2.x format built with HXCOMP.EXE 
        ///         Website A web site with basic frame set viewer page 
        ///         Help1xAndWebsite An HTML Help 1.x file and web site 
        ///         Help2xAndWebsite An HTML Help 2.x file and web site
        /// </remarks>
        private string helpFileFormat;

        /// <summary>
        /// Path to Sandcastle tool
        /// </summary>
        private string sandcastlePath;

        /// <summary>
        /// The output is logged to this buffer - and displayed if an error occurs
        /// </summary>
        private StringBuilder logBuffer = new StringBuilder();

        /// <summary>
        /// Initializes a new instance of the NDoc class.
        /// </summary>
        public SandcastleBuilder()
        {
        }

        /// <summary>
        /// Project File Path
        /// </summary>
        [Required]
        public string ProjectFilePath
        {
            get { return this.projectFilePath; }
            set { this.projectFilePath = value; }
        }

        /// <summary>
        /// Output Path
        /// </summary>
        [Required]
        public string OutputPath
        {
            get { return this.outputPath; }
            set { this.outputPath = value; }
        }

        /// <summary>
        /// Help File Format
        /// </summary>
        public string HelpFileFormat
        {
            get { return this.helpFileFormat; }
            set { this.helpFileFormat = value; }
        }

        /// <summary>
        /// Sandcastle Path
        /// </summary>
        public string SandcastlePath
        {
            get { return this.sandcastlePath; }
            set { this.sandcastlePath = value; }
        }

        /// <summary>
        /// Presentation Style
        /// </summary>
        public string PresentationStyle
        {
            get { return this.presentationStyle; }
            set { this.presentationStyle = value; }
        }

        /// <summary>
        /// Assemblies to document
        /// </summary>
        [Required]
        public ITaskItem[] Assemblies
        {
            get { return this.assemblies; }
            set { this.assemblies = value; }
        }
 
        /// <summary>
        /// The name of the exe we call.
        /// </summary>
        /// <value>The name of the exe we call.</value>
        protected override string ToolName
        {
            get { return "SandcastleBuilderConsole.exe"; }
        }

        /// <summary>
        /// Generates Full calculated path to tool including exe name
        /// </summary>
        /// <returns>Full calculated path to tool including exe name</returns>
        protected override string GenerateFullPathToTool()
        {
            if (false == string.IsNullOrEmpty(this.ToolPath))
            {
                return System.IO.Path.Combine(this.ToolPath, this.ToolName);
            }
            else
            {
                return this.ToolName;
            }
        }
   
        /// <summary>
        /// Performs the action of this task.
        /// </summary>
        protected override void InternalExecute()
        {
            #region Execute code

            // Create Help 1x or Help 2x Help files
            Log.LogMessageFromResources("SandcastleBuilder.CreatingDocumentation");

            if (!this.SetupSandcastleBuildProject())
            {
                throw new TaskException("SandcastleBuilder.ProjectSetupFailed", this.ProjectFilePath);
            }

            Directory.CreateDirectory(this.OutputPath);

            base.InternalExecute();

            if (this.ExitCode != 0)
            {
                Log.LogError(this.logBuffer.ToString());
                throw new TaskException("SandcastleBuilder.BuildFailed");
            }

            #endregion
        }
        
        /// <summary>
        /// Log the Sandcastle Faults to raise to higher importance if the tool fails
        /// </summary>
        /// <param name="line">Line to log</param>
        /// <param name="messageImportance">Message Importance</param>
        protected override void LogEventsFromTextOutput(string line, MessageImportance messageImportance)
        {
            this.logBuffer.Append(line);
            this.logBuffer.Append(Environment.NewLine);

            // Always log the Text Output
            base.LogEventsFromTextOutput(line, messageImportance);
        }

        /// <summary>
        /// Generate Command Line Commands
        /// </summary>
        /// <returns>Project File Path</returns>
        protected override string GenerateCommandLineCommands()
        {
            return this.projectFilePath;
        }

        /// <summary>
        /// Update the Sandcastle Help Builder Project file
        /// </summary>
        /// <returns>True if updated successfully</returns>
        private bool SetupSandcastleBuildProject()
        {
            Log.LogMessageFromResources(MessageImportance.Low, "SandcastleBuilder.AssembliesIncluded");
            foreach (ITaskItem filename in this.assemblies)
            {
                Log.LogMessage(MessageImportance.Low, filename.ItemSpec);
            }

            // What if assemblies has no elements?
            if (this.assemblies.Length == 0)
            {
                Log.LogError("SandcastleBuilder.NoAssembliesFound");
                return false;
            }

            XmlDocument masterXml = new XmlDocument();
            masterXml.Load(this.projectFilePath);            

            // Update assemblies
            XmlNode targetNode = masterXml.SelectSingleNode("project/assemblies");
            if (targetNode == null)
            {
                Log.LogError("SandcastleBuilder.ErrorLocatingAssemblies");
                return false;
            }

            targetNode.RemoveAll();

            foreach (ITaskItem assembly in this.assemblies)
            {
                XmlElement newElem = masterXml.CreateElement("assembly");
                XmlAttribute newAttr = masterXml.CreateAttribute("assemblyPath");
                newAttr.Value = assembly.ItemSpec;
                newElem.SetAttributeNode(newAttr);
                newAttr = masterXml.CreateAttribute("xmlCommentsPath");

                string documentationFile = Path.ChangeExtension(assembly.ItemSpec, ".xml");
                if (System.IO.File.Exists(documentationFile))
                {
                    // xmlCommentPath
                    newAttr.Value = documentationFile;
                    newElem.SetAttributeNode(newAttr);

                    // Comments Only
                    newAttr = masterXml.CreateAttribute("CommentsOnly");

                    string commentsOnly = assembly.GetMetadata("CommentsOnly");
                    if (string.IsNullOrEmpty(commentsOnly))
                    {
                        newAttr.Value = CommentsOnlyDefault;
                        newElem.SetAttributeNode(newAttr);
                    }
                    else
                    {
                        bool commentsOnlyFlag;
                        
                        // validate that the comments Only flag is a valid bool, and format as per standard boolean format (i.e. True, False)
                        if (bool.TryParse(commentsOnly, out commentsOnlyFlag))
                        {
                            newAttr.Value = commentsOnlyFlag.ToString();
                            newElem.SetAttributeNode(newAttr);
                        }
                        else
                        {
                            Log.LogError("SandcastleBuilder.InvalidCommentsOnlyValue", assembly.ItemSpec);
                            return false;                            
                        }
                    }
                    
                    targetNode.AppendChild(newElem);                
                }
                else
                {
                    Log.LogError("SandcastleBuilder.MissingXmlFile", documentationFile);
                    return false;
                }
            }

            // Update sandcastlePath
            if (false == string.IsNullOrEmpty(this.sandcastlePath))
            {
                targetNode = masterXml.SelectSingleNode("project/SandcastlePath");

                if (targetNode == null)
                {
                    targetNode = masterXml.SelectSingleNode("project/sandcastlePath");
                }

                if (targetNode == null)
                {
                    Log.LogError("SandcastleBuilder.ErrorLocatingSandcastlePath");
                    return false;
                }

                targetNode.InnerText = this.sandcastlePath;
            }            

            // Update outputPath
            if (false == string.IsNullOrEmpty(this.outputPath))
            {
                targetNode = masterXml.SelectSingleNode("project/OutputPath");

                if (targetNode == null)
                {
                    targetNode = masterXml.SelectSingleNode("project/outputPath");
                }

                if (targetNode == null)
                {
                    Log.LogError("SandcastleBuilder.ErrorLocatingOutputPath");
                    return false;
                }

                targetNode.InnerText = this.outputPath;
            }

            // Update help file format
            if (false == string.IsNullOrEmpty(this.helpFileFormat))
            {
                targetNode = masterXml.SelectSingleNode("project/HelpFileFormat");
                
                if (targetNode == null)
                {
                    targetNode = masterXml.SelectSingleNode("project/helpFileFormat");
                }

                if (targetNode == null)
                {
                    Log.LogError("SandcastleBuilder.ErrorLocatingHelpFileFormat");
                    return false;
                }

                targetNode.InnerText = this.helpFileFormat;
            }            

            // Update presentationStyle
            if (false == string.IsNullOrEmpty(this.presentationStyle))
            {
                targetNode = masterXml.SelectSingleNode("project/PresentationStyle");

                if (targetNode == null)
                {
                    targetNode = masterXml.SelectSingleNode("project/presentationStyle");
                }

                if (targetNode == null)
                {
                    Log.LogError("SandcastleBuilder.ErrorLocatingPresentationStyle");                                  
                    return false;
                }

                targetNode.InnerText = this.presentationStyle;
            }            

            // Save the document, and set the attrbutes back to what they were not just read only
            FileInfo fi = new FileInfo(this.projectFilePath);
            FileAttributes oldAttributes = fi.Attributes;
            fi.Attributes = FileAttributes.Normal;
            masterXml.Save(this.projectFilePath);
            fi.Attributes = oldAttributes;
            return true;
        }        
   }
}